package com.logica.mdm.webservices.utils;

import javax.transaction.UserTransaction;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.aop.TargetSource;

import com.siperian.sif.client.EjbSiperianClient;
import com.siperian.sif.client.SiperianClient;

/**
 * Utility methods for executing requests with a SiperianClient
 * instance in a pool, optionally with transaction support.
 * @author Fabien Crespel <fabien.crespel@logica.com>
 */
public class SiperianPoolUtils {
	
	private static final Logger logger = LoggerFactory.getLogger(SiperianPoolUtils.class);

	/**
	 * Execute a SiperianRunnable with a SiperianClient from a pool.
	 * This execution is not wrapped by a transaction.
	 * @param siperianClientPool
	 * @param runnable
	 * @throws Throwable
	 */
	public static void executeRunnable(TargetSource siperianClientPool, SiperianRunnable runnable) throws Throwable {
		executeRunnable(siperianClientPool, runnable, false);
	}
	
	/**
	 * Execute a SiperianRunnable with a SiperianClient from a pool.
	 * This execution is automatically wrapped by a transaction.
	 * @param siperianClientPool
	 * @param runnable
	 * @throws Throwable
	 */
	public static void executeRunnableTransacted(TargetSource siperianClientPool, SiperianRunnable runnable) throws Throwable {
		executeRunnable(siperianClientPool, runnable, true);
	}
	
	/**
	 * Execute a SiperianRunnable with a SiperianClient from a pool.
	 * The whole execution may optionally be wrapped by a transaction
	 * that is committed on success, and rolled back on exception.
	 * @param siperianClientPool TargetSource that will provide a SiperianClient instance
	 * @param runnable the SiperianRunnable to execute
	 * @param transacted true if the whole execution should be wrapped by a transaction
	 * @throws Throwable
	 */
	protected static void executeRunnable(TargetSource siperianClientPool, SiperianRunnable runnable, boolean transacted) throws Throwable {
		if (siperianClientPool == null)
			throw new IllegalArgumentException("siperianClientPool cannot be null");
		else if (runnable == null)
			throw new IllegalArgumentException("runnable cannot be null");
		
		SiperianClient siperianClient = null;
		UserTransaction transaction = null;
		try {
			siperianClient = (SiperianClient) siperianClientPool.getTarget();
			
			// Create the transaction if necessary
			if (transacted) {
				if (siperianClient instanceof EjbSiperianClient)
					transaction = ((EjbSiperianClient)siperianClient).createTX(60);
				else
					logger.warn("Cannot create a transaction when SiperianClient doesn't use the EJB protocol, CHECK YOUR CONFIGURATION.");
			}
			
			// TX Begin
			if (transaction != null) transaction.begin();
			
			// Run the runnable (perform requests, etc.)
			runnable.setSiperianClient(siperianClient);
			runnable.setTransaction(transaction);
			runnable.doRun();
			
			// TX Commit
			if (transaction != null) transaction.commit();
			
		} catch (Throwable e) {
			// TX Rollback
			try {
				if (transaction != null) transaction.rollback();
			} catch (Exception ignored) {
			}
			
			throw e;
			
		} finally {
			// Always remove the siperianClient and transaction from the runnable
			runnable.setSiperianClient(null);
			runnable.setTransaction(null);
			
			// Always return the siperianClient to its pool
			try {
				siperianClientPool.releaseTarget(siperianClient);
			} catch (Exception e) {
				// Log the error, but do not propagate it to the client
				logger.error("Error releasing SiperianClient to the pool.", e);
			}
		}
	}
}
